package beeapi

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/big"
	"net/http"
	"strings"
	"time"

	"github.com/tidwall/gjson"
)

const (
	Port = 1635
)

var (
	ErrorStatus = fmt.Errorf("http-status")
)

var client *http.Client

func init() {
	client = newClient()
}

func newClient() *http.Client {
	transport := &http.Transport{
		TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		IdleConnTimeout:     300 * time.Second,
		MaxIdleConns:        1000,
		MaxIdleConnsPerHost: 500,
	}
	client := &http.Client{
		Transport: transport,
		Timeout:   10 * time.Second,
	}
	return client
}

// method: POST, GET
func Call(method, url string) (*gjson.Result, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	rsp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != 200 {
		//log.Println("http-status:", rsp.StatusCode)
		return nil, ErrorStatus
	}

	data, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, err
	}
	result := gjson.ParseBytes(data)
	return &result, nil
}

func Get(url string) (*gjson.Result, error) {
	return Call("GET", url)
}

func Post(url string) (*gjson.Result, error) {
	return Call("POST", url)
}

func baseUrl(ip string) string {
	return fmt.Sprintf("http://%s:%d", ip, Port)
}

type HealthResult struct {
	Status  string `json:"status"`
	Version string `json:"version"`
}

func Health(ip string) (*HealthResult, error) {
	url := baseUrl(ip) + "/health"
	r, err := Get(url)
	if err != nil {
		return nil, err
	}
	ver := r.Get("version").String()
	i := strings.Index(ver, "-")
	if i >= 0 {
		ver = ver[:i]
	}
	out := &HealthResult{
		Status:  r.Get("status").String(),
		Version: ver,
	}
	return out, nil
}

type AddressesResult struct {
	Overlay   string `json:"overlay"`
	Ethereum  string `json:"ethereum"`
	PublicKey string `json:"public_key"`
}

// 获取节点地址/以太钱包地址
func Addresses(ip string) (*AddressesResult, error) {
	url := baseUrl(ip) + "/addresses"
	r, err := Get(url)
	if err != nil {
		return nil, err
	}
	out := &AddressesResult{
		Overlay:   r.Get("overlay").String(),
		Ethereum:  r.Get("ethereum").String(),
		PublicKey: r.Get("public_key").String(),
	}
	return out, nil
}

// 支票本地址
func ChequebookAddress(ip string) (string, error) {
	url := baseUrl(ip) + "/chequebook/address"
	r, err := Get(url)
	if err != nil {
		return "", err
	}
	// v0.5.3
	if r.Get("chequebookaddress").Exists() {
		return r.Get("chequebookaddress").String(), nil
	}
	return r.Get("chequebookAddress").String(), nil
}

// 支票本余额
type ChequebookBalanceResult struct {
	TotalBalance     string
	AvailableBalance string
}

func ChequebookBalance(ip string) (*ChequebookBalanceResult, error) {
	url := baseUrl(ip) + "/chequebook/balance"
	r, err := Get(url)
	if err != nil {
		return nil, err
	}
	out := &ChequebookBalanceResult{
		TotalBalance:     r.Get("totalBalance").String(),
		AvailableBalance: r.Get("availableBalance").String(),
	}
	return out, nil
}

type LastReceived struct {
	Beneficiary string  `json:"beneficiary"`
	Chequebook  string  `json:"chequebook"`
	Payout      big.Int `json:"payout"` // 支票额度
}

type PeerCheque struct {
	Peer         string        `json:"peer"`
	LastReceived *LastReceived `json:"lastreceived"`
}

type ChequebookChequeResult struct {
	Lastcheques []*PeerCheque `json:"lastcheques"`
}

// 获取所有开给我的支票peer列表
func Cheques(ip string) ([]*PeerCheque, error) {
	url := baseUrl(ip) + "/chequebook/cheque"
	r, err := Get(url)
	if err != nil {
		return nil, err
	}
	var out = &ChequebookChequeResult{}
	err = json.Unmarshal([]byte(r.String()), &out)
	if err != nil {
		return nil, err
	}
	return out.Lastcheques, nil
}

type CashoutResultResult struct {
	Recipient  string  `json:"recipient"`
	LastPayout big.Int `json:"lastPayout"`
	Bounced    bool    `json:"bounced"`
}

type CashoutResult struct {
	Peer             string               `json:"peer"`
	Chequebook       string               `json:"chequebook"`
	CumulativePayout big.Int              `json:"cumulativePayout"` // 累计兑换额度 v0.5.3
	UncashedAmount   big.Int              `json:"uncashedAmount"`   // 尚未兑换额度 v0.6.0
	Beneficiary      string               `json:"beneficiary"`      // 收益人eth地址
	TransactionHash  string               `json:"transactionHash"`
	Result           *CashoutResultResult `json:"result"`
	V6               bool                 `json:"-"` // v0.6.x用uncashedAmount, v0.5.x用cumulativePayout
}

// 获取我从对方开过的支票总记录
func Cashedout(ip string, peer string) (*CashoutResult, error) {
	url := baseUrl(ip) + "/chequebook/cashout/" + peer
	var out = &CashoutResult{
		Peer: peer,
	}
	r, err := Get(url)
	if err == ErrorStatus {
		return out, nil
	}
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal([]byte(r.String()), &out)
	if err != nil {
		return nil, err
	}
	out.V6 = r.Get("uncashedAmount").Exists()
	return out, nil
}

// 执行兑换支票
// 返回 (transactionHash, error)
func DoCashout(ip string, peer string) (string, error) {
	url := baseUrl(ip) + "/chequebook/cashout/" + peer
	r, err := Post(url)
	if err != nil {
		return "", err
	}
	tx := r.Get("transactionHash").String()
	return tx, nil
}

// peers连接数
func Peers(ip string) (int, error) {
	url := baseUrl(ip) + "/peers"
	r, err := Get(url)
	if err != nil {
		return 0, err
	}
	n := len(r.Get("peers").Array())
	return n, nil
}
